spring websocket实现前后端通信_网络_super的博客-CSDN博客

创建时间:2020/3/24 11:10
来源:https://blog.csdn.net/runbat/article/details/80985944


  项目要用websocket实现一个前后端实时通信的功能,做完之后感触颇多,写个博客回顾下整个历程,也希望能给后面的同志有点帮助。

百度网盘示例源码:链接:https://pan.baidu.com/s/1Gi3qRyLO-lTnkVn4MqGIJA 密码:4ovr

我使用springmvc的websocket组件,官网地址:点击打开链接

示例内容:用户登陆之后往设置session设置登陆名,之后跳转到发送消息页面,加载页面时创建websocket连接,这时,springmvc拦截器拦截到websocket请求,把session中登陆名保存到attributes中,这个值会映射到WebSocketSession里,从而在SpringWebSocketHandler类中使用, 这部分看不懂没关联,结合下面的代码来看就懂了。

步骤一:添加maven依赖,注意两点问题

1、spring的websocket依赖容器支持,我选用的是tomcat7.077,tomcat7以下是不支持websocket的

2、javax-servlet-api和java-websocket-api两个包都限定了<scope>provided</scope>因为这两个包tomcat容器已经带了,provided表示编译时使用,打包不会包含在war包里,如不知道重启启动会报错。

  1. 1
    <dependency>
  2. 2
    <groupId>javax.servlet</groupId>
  3. 3
    <artifactId>javax.servlet-api</artifactId>
  4. 4
    <version>3.1.0</version>
  5. 5
    <scope>provided</scope>
  6. 6
    </dependency>
  7. 7
    <dependency>
  8. 8
    <groupId>org.springframework</groupId>
  9. 9
    <artifactId>spring-webmvc</artifactId>
  10. 10
    <version>4.1.5.RELEASE</version>
  11. 11
    </dependency>
  12. 12
    <dependency>
  13. 13
    <groupId>jstl</groupId>
  14. 14
    <artifactId>jstl</artifactId>
  15. 15
    <version>1.2</version>
  16. 16
    </dependency>
  17. 17
    <dependency>
  18. 18
    <groupId>taglibs</groupId>
  19. 19
    <artifactId>standard</artifactId>
  20. 20
    <version>1.1.2</version>
  21. 21
    </dependency>
  22. 22
  23. 23
    <!-- spring websocket -->
  24. 24
    <dependency>
  25. 25
    <groupId>org.springframework</groupId>
  26. 26
    <artifactId>spring-messaging</artifactId>
  27. 27
    <version>4.1.7.RELEASE</version>
  28. 28
    </dependency>
  29. 29
    <dependency>
  30. 30
    <groupId>org.springframework</groupId>
  31. 31
    <artifactId>spring-websocket</artifactId>
  32. 32
    <version>4.1.7.RELEASE</version>
  33. 33
    </dependency>
  34. 34
    <dependency>
  35. 35
    <groupId>javax.websocket</groupId>
  36. 36
    <artifactId>javax.websocket-api</artifactId>
  37. 37
    <version>1.0</version>
  38. 38
    <scope>provided</scope>
  39. 39
    </dependency>

步骤二:编辑SpringWebSocketConfig,根据spring文档,编写websocketConfig,这里可参看文档,xml配置和使用注解两种方式,我选择注解方式

registerWebSocketHandlers:这个方法是向spring容器注册一个handler地址,我把他理解成requestMapping

addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来搞事情。

setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口

withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。

  1. 1
    @Configuration
  2. 2
    @EnableWebSocket
  3. 3
    public class SpringWebSocketConfig implements WebSocketConfigurer {
  4. 4
  5. 5
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  6. 6
    registry.addHandler(webSocketHandler(),"/websocket/socketServer")
  7. 7
    .addInterceptors(new SpringWebSocketHandlerInterceptor()).setAllowedOrigins("*");
  8. 8
  9. 9
    registry.addHandler(webSocketHandler(), "/sockjs/socketServer").setAllowedOrigins("http://localhost:28180")
  10. 10
    .addInterceptors(new SpringWebSocketHandlerInterceptor()).withSockJS();
  11. 11
    }
  12. 12
  13. 13
    @Bean
  14. 14
    public TextWebSocketHandler webSocketHandler(){
  15. 15
  16. 16
    return new SpringWebSocketHandler();
  17. 17
    }
  18. 18
  19. 19
    }

步骤三:编写SpringWebSocketHandlerInterceptor

这个是创建websocket连接是的拦截器,记录建立连接的用户的session以便根据不同session来通信

  1. 1
    public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
  2. 2
    @Override
  3. 3
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
  4. 4
    Map<String, Object> attributes) throws Exception {
  5. 5
    System.out.println("Before Handshake");
  6. 6
    if (request instanceof ServletServerHttpRequest) {
  7. 7
    ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
  8. 8
    HttpSession session = servletRequest.getServletRequest().getSession(false);
  9. 9
    if (session != null) {
  10. 10
    //使用userName区分WebSocketHandler,以便定向发送消息
  11. 11
    String userName = (String) session.getAttribute("SESSION_USERNAME"); //一般直接保存user实体
  12. 12
    if (userName!=null) {
  13. 13
    attributes.put("WEBSOCKET_USERID",userName);
  14. 14
    }
  15. 15
  16. 16
    }
  17. 17
    }
  18. 18
    return super.beforeHandshake(request, response, wsHandler, attributes);
  19. 19
  20. 20
    }
  21. 21
  22. 22
    @Override
  23. 23
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
  24. 24
    Exception ex) {
  25. 25
    super.afterHandshake(request, response, wsHandler, ex);
  26. 26
    }
  27. 27
  28. 28
    }

步骤四:编写SpringWebSocketHandler 

  1. 1
    public class SpringWebSocketHandler extends TextWebSocketHandler {
  2. 2
  3. 3
  4. 4
    private static final Map<String, WebSocketSession> users; //Map来存储WebSocketSession,key用USER_ID 即在线用户列表
  5. 5
  6. 6
    //用户标识
  7. 7
    private static final String USER_ID = "WEBSOCKET_USERID"; //对应监听器从的key
  8. 8
  9. 9
  10. 10
    static {
  11. 11
    users = new HashMap<String, WebSocketSession>();
  12. 12
    }
  13. 13
  14. 14
    public SpringWebSocketHandler() {}
  15. 15
  16. 16
    /**
  17. 17
    * 连接成功时候,会触发页面上onopen方法
  18. 18
    */
  19. 19
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  20. 20
  21. 21
    System.out.println("成功建立websocket连接!");
  22. 22
    String userId = (String) session.getAttributes().get(USER_ID);
  23. 23
    users.put(userId,session);
  24. 24
    System.out.println("当前线上用户数量:"+users.size());
  25. 25
  26. 26
    //这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
  27. 27
    //TextMessage returnMessage = new TextMessage("成功建立socket连接,你将收到的离线");
  28. 28
    //session.sendMessage(returnMessage);
  29. 29
    }
  30. 30
  31. 31
    /**
  32. 32
    * 关闭连接时触发
  33. 33
    */
  34. 34
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
  35. 35
    String userId= (String) session.getAttributes().get(USER_ID);
  36. 36
    System.out.println("用户"+userId+"已退出!");
  37. 37
    users.remove(userId);
  38. 38
    System.out.println("剩余在线用户"+users.size());
  39. 39
    }
  40. 40
  41. 41
    /**
  42. 42
    * js调用websocket.send时候,会调用该方法
  43. 43
    */
  44. 44
    @Override
  45. 45
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  46. 46
  47. 47
    super.handleTextMessage(session, message);
  48. 48
  49. 49
    /**
  50. 50
    * 收到消息,自定义处理机制,实现业务
  51. 51
    */
  52. 52
    System.out.println("服务器收到消息:"+message);
  53. 53
  54. 54
    if(message.getPayload().startsWith("#anyone#")){ //单发某人
  55. 55
  56. 56
    sendMessageToUser((String)session.getAttributes().get(USER_ID), new TextMessage("服务器单发:" +message.getPayload())) ;
  57. 57
  58. 58
    }else if(message.getPayload().startsWith("#everyone#")){
  59. 59
  60. 60
    sendMessageToUsers(new TextMessage("服务器群发:" +message.getPayload()));
  61. 61
  62. 62
    }else{
  63. 63
  64. 64
    }
  65. 65
  66. 66
    }
  67. 67
  68. 68
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  69. 69
    if(session.isOpen()){
  70. 70
    session.close();
  71. 71
    }
  72. 72
    System.out.println("传输出现异常,关闭websocket连接... ");
  73. 73
    String userId= (String) session.getAttributes().get(USER_ID);
  74. 74
    users.remove(userId);
  75. 75
    }
  76. 76
  77. 77
    public boolean supportsPartialMessages() {
  78. 78
  79. 79
    return false;
  80. 80
    }
  81. 81
  82. 82
  83. 83
    /**
  84. 84
    * 给某个用户发送消息
  85. 85
    *
  86. 86
    * @param userId
  87. 87
    * @param message
  88. 88
    */
  89. 89
    public void sendMessageToUser(String userId, TextMessage message) {
  90. 90
    for (String id : users.keySet()) {
  91. 91
    if (id.equals(userId)) {
  92. 92
    try {
  93. 93
    if (users.get(id).isOpen()) {
  94. 94
    users.get(id).sendMessage(message);
  95. 95
    }
  96. 96
    } catch (IOException e) {
  97. 97
    e.printStackTrace();
  98. 98
    }
  99. 99
    break;
  100. 100
    }
  101. 101
    }
  102. 102
    }
  103. 103
  104. 104
    /**
  105. 105
    * 给所有在线用户发送消息
  106. 106
    *
  107. 107
    * @param message
  108. 108
    */
  109. 109
    public void sendMessageToUsers(TextMessage message) {
  110. 110
    for (String userId : users.keySet()) {
  111. 111
    try {
  112. 112
    if (users.get(userId).isOpen()) {
  113. 113
    users.get(userId).sendMessage(message);
  114. 114
    }
  115. 115
    } catch (IOException e) {
  116. 116
    e.printStackTrace();
  117. 117
    }
  118. 118
    }
  119. 119
    }
  120. 120
  121. 121
    }

步骤五:配置文件扫描config类  我的SpringWebSocketConfig配置在包com.thunisoft.config下

	<context:component-scan base-package="com.thunisoft.ssm.controller,com.thunisoft.config"></context:component-scan>

步骤六:编写springmvc controller

  1. 1
    @Controller
  2. 2
    public class WebSocketController {
  3. 3
  4. 4
    @Bean//这个注解会从Spring容器拿出Bean
  5. 5
    public SpringWebSocketHandler infoHandler() {
  6. 6
  7. 7
    return new SpringWebSocketHandler();
  8. 8
    }
  9. 9
  10. 10
  11. 11
    @RequestMapping("/websocket/loginPage")
  12. 12
    public String loginPage(HttpServletRequest request, HttpServletResponse response) throws Exception {
  13. 13
    return "/order/login";
  14. 14
    }
  15. 15
  16. 16
  17. 17
    @RequestMapping("/websocket/login")
  18. 18
    public String login(HttpServletRequest request, HttpServletResponse response) throws Exception {
  19. 19
    String username = request.getParameter("username");
  20. 20
    System.out.println(username+"登录");
  21. 21
    HttpSession session = request.getSession(false);
  22. 22
    session.setAttribute("SESSION_USERNAME", username); //一般直接保存user实体
  23. 23
    return "/order/send";
  24. 24
    }
  25. 25
  26. 26
    @RequestMapping("/websocket/send")
  27. 27
    @ResponseBody
  28. 28
    public String send(HttpServletRequest request) {
  29. 29
    String username = request.getParameter("username");
  30. 30
    infoHandler().sendMessageToUser(username, new TextMessage("你好,测试!!!!"));
  31. 31
    return null;
  32. 32
    }
  33. 33
  34. 34
  35. 35
    @RequestMapping("/websocket/broad")
  36. 36
    @ResponseBody
  37. 37
    public String broad() {
  38. 38
    infoHandler().sendMessageToUsers(new TextMessage("发送一条小Broad"));
  39. 39
    System.out.println("群发成功");
  40. 40
    return "broad";
  41. 41
    }
  42. 42
  43. 43
    }

步骤七:编辑登陆jsp

  1. 1
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  2. 2
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  3. 3
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
  4. 4
    <c:set var="ctx" value="${pageContext.request.contextPath}"/>
  5. 5
    <!DOCTYPE html>
  6. 6
    <html>
  7. 7
    <head>
  8. 8
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  9. 9
    <title>测试spring websocket</title>
  10. 10
    </head>
  11. 11
    <body>
  12. 12
  13. 13
    <form action="${ctx}/websocket/login">
  14. 14
    登录名:<input type="text" name="username"/>
  15. 15
    <input type="submit" value="登录聊天室"/>
  16. 16
    </form>
  17. 17
    </body>

步骤八:编写通信页面

  1. 1
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  2. 2
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  3. 3
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
  4. 4
    <c:set var="ctx" value="${pageContext.request.contextPath}"/>
  5. 5
    <!DOCTYPE html>
  6. 6
    <html>
  7. 7
    <head>
  8. 8
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  9. 9
    <title></title>
  10. 10
    <!--
  11. 11
    <link rel="stylesheet" href="/css/style.css"/>
  12. 12
    -->
  13. 13
    <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
  14. 14
    <script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
  15. 15
    <script type="text/javascript">
  16. 16
    var websocket = null;
  17. 17
    if ('WebSocket' in window) {
  18. 18
    websocket = new WebSocket("ws://localhost:8080/springfirst/websocket/socketServer");
  19. 19
    }
  20. 20
    else if ('MozWebSocket' in window) {
  21. 21
    websocket = new MozWebSocket("ws://localhost:8080/springfirst/websocket/socketServer");
  22. 22
    }
  23. 23
    else {
  24. 24
    websocket = new SockJS("http://localhost:8080/springfirst/sockjs/socketServer");
  25. 25
    }
  26. 26
    websocket.onopen = onOpen;
  27. 27
    websocket.onmessage = onMessage;
  28. 28
    websocket.onerror = onError;
  29. 29
    websocket.onclose = onClose;
  30. 30
  31. 31
    function onOpen(openEvt) {
  32. 32
    alert(openEvt.Data);
  33. 33
    }
  34. 34
  35. 35
    function onMessage(evt) {
  36. 36
    alert("super is:" + evt.data);
  37. 37
    }
  38. 38
    function onOpen() {
  39. 39
    }
  40. 40
    function onError() {}
  41. 41
    function onClose() {}
  42. 42
  43. 43
    function doSendUser() {
  44. 44
  45. 45
    alert(websocket.readyState + ":" + websocket.OPEN);
  46. 46
    if (websocket.readyState == websocket.OPEN) {
  47. 47
    var msg = document.getElementById("inputMsg").value;
  48. 48
    websocket.send("#anyone#"+msg);//调用后台handleTextMessage方法
  49. 49
    alert("发送成功!");
  50. 50
    } else {
  51. 51
    alert("连接失败!");
  52. 52
    }
  53. 53
    }
  54. 54
  55. 55
  56. 56
    function doSendUsers() {
  57. 57
    if (websocket.readyState == websocket.OPEN) {
  58. 58
    var msg = document.getElementById("inputMsg").value;
  59. 59
    websocket.send("#everyone#"+msg);//调用后台handleTextMessage方法
  60. 60
    alert("发送成功!");
  61. 61
    } else {
  62. 62
    alert("连接失败!");
  63. 63
    }
  64. 64
    }
  65. 65
  66. 66
  67. 67
    window.close=function()
  68. 68
    {
  69. 69
    websocket.onclose();
  70. 70
    }
  71. 71
    function websocketClose() {
  72. 72
    websocket.close();
  73. 73
    }
  74. 74
    </script>
  75. 75
  76. 76
    </head>
  77. 77
    <body>
  78. 78
  79. 79
    请输入:<textarea rows="5" cols="10" id="inputMsg" name="inputMsg"></textarea>
  80. 80
    <button οnclick="doSendUser();">发送</button>
  81. 81
    <button οnclick="doSendUsers();">群发</button>
  82. 82
    <button οnclick="websocketClose();">关闭连接</button>
  83. 83
    </body>
  84. 84
    </html>

演示效果图

登陆:


单发:

群发:


后续配合nginx发布,在nginx代理的情况下需要配置,这个示例项目,我的跟目录是/springfirst ,springfirst是我的项目名,实际项目发布的时候是隐藏项目名的,所以配置/就可以。

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
                  proxy_set_header Connection "upgrade";

  1. 1
    upstream websocket {
  2. 2
    server 127.0.0.1:8080;
  3. 3
    }
  4. 4
  5. 5
  6. 6
        server {
  7. 7
            listen       80;
  8. 8
    server_name  max.eqshow.cnn;
  9. 9
  10. 10
  11. 11
            #charset koi8-r;
  12. 12
  13. 13
  14. 14
            #access_log  logs/host.access.log  main;
  15. 15
    location /springfirst {
  16. 16
      proxy_pass http://websocket;
  17. 17
      proxy_set_header   Host             $host;
  18. 18
                      proxy_set_header   X-Real-IP        $remote_addr;
  19. 19
                      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  20. 20
      proxy_http_version 1.1;
  21. 21
      proxy_set_header Upgrade $http_upgrade;
  22. 22
                      proxy_set_header Connection "upgrade";
  23. 23
            }
  24. 24
    }

总结: 做完之后感觉整个过程并不复杂,单其实经历了好几天,从最初的不知道什么是websocket,到后来不知道nginx配置出问题,从不知道到知道,其实遇到了很多问题。比如,不知道websocket需要容器和浏览器的支持,不知道跨域需要设置setAllowedOrigins("*"),demo写好怎么融入到项目中也遇到了很多问题。但这一切在有结果的时候都豁然开朗。同时也了解了器用分析法,先了解基本用法,写个demo,然后研究其原理。到目前为止也是浅薄的是了解而已,后面遇到问题在继续更新吧。

本文示例参考:https://blog.csdn.net/zmx729618/article/details/78584633

springmvc websocket:点击打开链接

http://www.runoob.com:点击打开链接

发布了34 篇原创文章 · 获赞 26 · 访问量 8万+